#include "SffManager.h"

typedef struct __attribute__((packed)) {
    uint8_t r;
    uint8_t g;
    uint8_t b;
} rgb_t;

Palette generatePaletteFromRgbaArray(uint32_t pal_rgba[256])
{
    Palette p;
    // Convert the RGBA values into bytes (0-255 range for each channel)
    for (int i = 0; i < 256; i++) {
        p.ColorArray[i].R = (pal_rgba[i] >> 0) & 0xFF;
        p.ColorArray[i].G = (pal_rgba[i] >> 8) & 0xFF;
        p.ColorArray[i].B = (pal_rgba[i] >> 16) & 0xFF;
        p.ColorArray[i].A = (pal_rgba[i] >> 24) & 0xFF;  // Alpha
    }
    return p;
}

Palette generatePaletteFromRgbArray(rgb_t pal_rgb[256])
{
    Palette p;
    // Convert the RGBA values into bytes (0-255 range for each channel)
    for (int i = 0; i < 256; i++) {
        p.ColorArray[i].R = i ? pal_rgb[i].r : 0;
        p.ColorArray[i].G = i ? pal_rgb[i].g : 0;
        p.ColorArray[i].B = i ? pal_rgb[i].b : 0;
        p.ColorArray[i].A = i ? 255 : 0;  // Set alpha to 0 for the first color (or 1.0 if required)
    }
    return p;
}

std::vector<Color> generateColorListFromRgbaData(uint w, uint h, uint8_t *sprData)
{
    std::vector<Color> colorList;
    uint8_t *p = sprData;
    uint8_t *endP = sprData + w * h * 4;
    while (p != endP) {
        Color color;
        color.R = *p;
        p++;
        color.G = *p;
        p++;
        color.B = *p;
        p++;
        color.A = *p;
        p++;

        colorList.push_back(color);
    }

    return colorList;
}

std::vector<Color> generateColorListFromPaletteData(uint w, uint h, uint8_t *paIndexData, const Palette &pa)
{
    std::vector<Color> colorList;
    uint8_t *p = paIndexData;
    uint8_t *endP = paIndexData + w * h;
    while (p != endP) {
        Color color = pa.ColorArray[*p];
        p++;

        colorList.push_back(color);
    }

    return colorList;
}

void printSprite(Sprite *sprite)
{
    char c[255];
    sprintf(c, "Sprite: Group %d, Number %d, Size (%d,%d), Offset (%d,%d), PalIdx %d, Rle %d, ColDepth %d\n",
           sprite->Group, sprite->Number,
           sprite->W, sprite->H,
           sprite->OX, sprite->OY,
           sprite->PalIdx, -sprite->Rle, sprite->ColDepth);
    qDebug() << Q_FUNC_INFO << c;
}

int readSffHeader(Sff *sff, FILE *file, uint32_t *lofs, uint32_t *tofs)
{
    // Validate header by comparing 12 first bytes with "ElecbyteSpr\x0"
    char headerCheck[12];
    fread(headerCheck, 12, 1, file);
    if (memcmp(headerCheck, "ElecbyteSpr\0", 12) != 0) {
        qCritical() << Q_FUNC_INFO << "Invalid SFF file [%s]\n" << headerCheck;
        return -1;
    }

    // Read versions in the header
    if (fread(&sff->Header.Ver3, 1, 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading version\n";
        return -1;
    }
    if (fread(&sff->Header.Ver2, 1, 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading version\n";
        return -1;
    }
    if (fread(&sff->Header.Ver1, 1, 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading version\n";
        return -1;
    }
    if (fread(&sff->Header.Ver0, 1, 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading version\n";
        return -1;
    }
    uint32_t dummy;
    if (fread(&dummy, sizeof(uint32_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading dummy\n";
        return -1;
    }

    if (sff->Header.Ver0 == 2) {
        for (int i = 0; i < 4; i++) {
            if (fread(&dummy, sizeof(uint32_t), 1, file) != 1) {
                qCritical() << Q_FUNC_INFO << "Error reading dummy\n";
                return -1;
            }
        }
        // read FirstSpriteHeaderOffset
        if (fread(&sff->Header.FirstSpriteHeaderOffset, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading FirstSpriteHeaderOffset\n";
            return -1;
        }
        // read NumberOfSprites
        if (fread(&sff->Header.NumberOfSprites, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading NumberOfSprites\n";
            return -1;
        }
        // read FirstPaletteHeaderOffset
        if (fread(&sff->Header.FirstPaletteHeaderOffset, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading FirstPaletteHeaderOffset\n";
            return -1;
        }
        // read NumberOfPalettes
        if (fread(&sff->Header.NumberOfPalettes, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading NumberOfPalettes\n";
            return -1;
        }
        // read lofs
        if (fread(lofs, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading lofs\n";
            return -1;
        }
        if (fread(&dummy, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading dummy\n";
            return -1;
        }
        // read tofs
        if (fread(tofs, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading tofs\n";
            return -1;
        }
    } else if (sff->Header.Ver0 == 1) {
        // read NumberOfSprites
        if (fread(&sff->Header.NumberOfSprites, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading NumberOfSprites\n";
            return -1;
        }
        // read FirstSpriteHeaderOffset
        if (fread(&sff->Header.FirstSpriteHeaderOffset, sizeof(uint32_t), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading FirstSpriteHeaderOffset\n";
            return -1;
        }
        sff->Header.FirstPaletteHeaderOffset = 0;
        sff->Header.NumberOfPalettes = 0;
        *lofs = 0;
        *tofs = 0;
    } else {
        qCritical() << Q_FUNC_INFO << "Unsupported SFF version: %d\n" << sff->Header.Ver0;
        return -1;
    }

    return 0;
}

int readSpriteHeaderV1(Sprite &sprite, FILE *file, uint32_t *ofs, uint32_t *size, uint16_t *link)
{
    // Read ofs
    if (fread(ofs, sizeof(uint32_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading ofs\n";
        return -1;
    }
    // Read size
    if (fread(size, sizeof(uint32_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading size\n";
        return -1;
    }
    if (fread(&sprite.OX, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite offset\n";
        return -1;
    }
    if (fread(&sprite.OY, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite offset\n";
        return -1;
    }
    // Read sprite header
    if (fread(&sprite.Group, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite group\n";
        return -1;
    }
    if (fread(&sprite.Number, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite number\n";
        return -1;
    }
    // Read the link to the next sprite header
    if (fread(link, sizeof(uint16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite link\n";
        return -1;
    }
    return 0;
}

int readSpriteHeaderV2(Sprite &sprite, FILE *file, uint32_t *ofs, uint32_t *size, uint32_t lofs, uint32_t tofs, uint16_t *link)
{
    // Read sprite header
    if (fread(&sprite.Group, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite group\n";
        return -1;
    }
    if (fread(&sprite.Number, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite number\n";
        return -1;
    }
    if (fread(&sprite.W, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite size\n";
        return -1;
    }
    if (fread(&sprite.H, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite size\n";
        return -1;
    }
    if (fread(&sprite.OX, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite offset\n";
        return -1;
    }
    if (fread(&sprite.OY, sizeof(int16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite offset\n";
        return -1;
    }
    // Read the link to the next sprite header
    if (fread(link, sizeof(uint16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite link\n";
        return -1;
    }
    char format;
    if (fread(&format, sizeof(char), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite format\n";
        return -1;
    }
    sprite.Rle = -format;
    // Read color depth
    if (fread(&sprite.ColDepth, sizeof(uint8_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading color depth\n";
        return -1;
    }
    // Read ofs
    if (fread(ofs, sizeof(uint32_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading ofs\n";
        return -1;
    }
    // Read size
    if (fread(size, sizeof(uint32_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading size\n";
        return -1;
    }
    uint16_t tmp;
    // Read tmp
    if (fread(&tmp, sizeof(uint16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading tmp\n";
        return -1;
    }
    sprite.PalIdx = tmp;
    // Read tmp
    if (fread(&tmp, sizeof(uint16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading tmp\n";
        return -1;
    }
    if ((tmp & 1) == 0) {
        *ofs += lofs;
    } else {
        *ofs += tofs;
    }

    return 0;
}

void spriteCopy(Sprite &dst, const Sprite &src)
{
    // dst.Group = src.Group;
    // dst.Number = src.Number;
    dst.W = src.W;
    dst.H = src.H;
    dst.OX = src.OX;
    dst.OY = src.OY;
    dst.PalIdx = src.PalIdx;
    dst.Rle = src.Rle;
    dst.ColDepth = src.ColDepth;
    dst.ColorList = src.ColorList;
}

uint8_t *RlePcxDecode(Sprite &s, uint8_t *srcPx, size_t srcLen)
{
    if (srcLen == 0) {
        qCritical() << Q_FUNC_INFO << "Warning: PCX data length is zero\n";
        return NULL;
    }

    size_t dstLen = s.W * s.H;
    uint8_t *dstPx = (uint8_t *) malloc(dstLen);
    if (!dstPx) {
        qCritical() << Q_FUNC_INFO << "Error allocating memory for PCX decoded data dstLen=%zu srcLen=%zu (%dx%d)\n"
                    << dstLen << srcLen << s.W << s.H;
        return NULL;
    }

    size_t i = 0; // input pointer
    size_t j = 0; // output pointer

    while (i < srcLen && j < dstLen) {
        uint8_t byte = srcPx[i++];
        int count = 1;

        if ((byte & 0xC0) == 0xC0) { // RLE marker
            count = byte & 0x3F;
            if (i < srcLen) {
                byte = srcPx[i++];
            } else {
                qCritical() << Q_FUNC_INFO << "Warning: RLE marker at end of data\n";
                break;
            }
        }

        while (count-- > 0 && j < dstLen) {
            dstPx[j++] = byte;
        }
    }

    if (j < dstLen) {
        qCritical() << Q_FUNC_INFO << "Warning: decoded PCX data shorter than expected (%zu vs %zu)\n" << j << dstLen;
        // Fill the remaining bytes with 0 (or a background color)
        memset(dstPx + j, 0, dstLen - j);
    }

    return dstPx;
}


uint8_t *Rle8Decode(Sprite &s, uint8_t *srcPx, size_t srcLen)
{
    if (srcLen == 0) {
        qCritical() << Q_FUNC_INFO << "Warning RLE8 data length is zero\n";
        return NULL;
    }

    size_t dstLen = s.W * s.H;
    uint8_t *dstPx = (uint8_t *) malloc(dstLen);
    if (!dstPx) {
        qCritical() << Q_FUNC_INFO << "Error allocating memory for RLE decoded data\n";
        return NULL;
    }
    size_t i = 0, j = 0;
    // Decode the RLE data
    while (j < dstLen) {
        long n = 1;
        uint8_t d = srcPx[i];
        if (i < (srcLen - 1)) {
            i++;
        }
        if ((d & 0xc0) == 0x40) {
            n = d & 0x3f;
            d = srcPx[i];
            if (i < (srcLen - 1)) {
                i++;
            }
        }
        for (; n > 0; n--) {
            if (j < dstLen) {
                dstPx[j] = d;
                j++;
            }
        }
    }
    return dstPx;
}

uint8_t *Rle5Decode(Sprite &s, uint8_t *srcPx, size_t srcLen)
{
    if (srcLen == 0) {
        qCritical() << Q_FUNC_INFO << "Warning RLE5 data length is zero\n";
        return NULL;
    }

    size_t dstLen = s.W * s.H;
    uint8_t *dstPx = (uint8_t *) malloc(dstLen);
    if (!dstPx) {
        qCritical() << Q_FUNC_INFO << "Error allocating memory for RLE decoded data\n";
        return NULL;
    }

    size_t i = 0, j = 0;
    while (j < dstLen) {
        int rl = (int) srcPx[i];
        if (i < srcLen - 1) {
            i++;
        }
        int dl = (int)(srcPx[i] & 0x7f);
        uint8_t c = 0;
        if (srcPx[i] >> 7 != 0) {
            if (i < srcLen - 1) {
                i++;
            }
            c = srcPx[i];
        }
        if (i < srcLen - 1) {
            i++;
        }
        while (1) {
            if (j < dstLen) {
                dstPx[j] = c;
                j++;
            }
            rl--;
            if (rl < 0) {
                dl--;
                if (dl < 0) {
                    break;
                }
                c = srcPx[i] & 0x1f;
                rl = (int)(srcPx[i] >> 5);
                if (i < srcLen - 1) {
                    i++;
                }
            }
        }
    }

    return dstPx;
}

uint8_t *Lz5Decode(Sprite &s, uint8_t *srcPx, size_t srcLen)
{
    if (srcLen == 0) {
        qCritical() << Q_FUNC_INFO << "Warning LZ5 data length is zero\n";
        return NULL;
    }

    size_t dstLen = s.W * s.H;
    uint8_t *dstPx = (uint8_t *) malloc(dstLen);
    if (!dstPx) {
        qCritical() << Q_FUNC_INFO << "Error allocating memory for LZ5 decoded data\n";
        return NULL;
    }

    // Decode the LZ5 data
    size_t i = 0, j = 0;
    long n = 0;
    uint8_t ct = srcPx[i], cts = 0, rb = 0, rbc = 0;
    if (i < srcLen - 1) {
        i++;
    }

    while (j < dstLen) {
        int d = (int) srcPx[i];
        if (i < srcLen - 1) {
            i++;
        }

        if (ct & (1 << cts)) {
            if ((d & 0x3f) == 0) {
                d = (d << 2 | (int) srcPx[i]) + 1;
                if (i < srcLen - 1) {
                    i++;
                }
                n = (int) srcPx[i] + 2;
                if (i < srcLen - 1) {
                    i++;
                }
            } else {
                rb |= (uint8_t)((d & 0xc0) >> rbc);
                rbc += 2;
                n = (int)(d & 0x3f);
                if (rbc < 8) {
                    d = (int) srcPx[i] + 1;
                    if (i < srcLen - 1) {
                        i++;
                    }
                } else {
                    d = (int) rb + 1;
                    rb = rbc = 0;
                }
            }
            for (;;) {
                if (j < dstLen) {
                    dstPx[j] = dstPx[j - d];
                    j++;
                }
                n--;
                if (n < 0) {
                    break;
                }
            }
        } else {
            if ((d & 0xe0) == 0) {
                n = (int) srcPx[i] + 8;
                if (i < srcLen - 1) {
                    i++;
                }
            } else {
                n = d >> 5;
                d &= 0x1f;
            }
            while (n-- > 0 && j < dstLen) {
                dstPx[j] = (uint8_t) d;
                j++;
            }
        }
        cts++;
        if (cts >= 8) {
            ct = srcPx[i];
            cts = 0;
            if (i < srcLen - 1) {
                i++;
            }
        }
    }

    return dstPx;
}

uint8_t *PngDecode(Sprite &s, uint8_t *data, uint32_t datasize)
{
    lodepng::State state;
    unsigned int width = 0, height = 0;

    unsigned status = lodepng_inspect(&width, &height, &state, data, datasize);
    if (status) {
        qCritical() << Q_FUNC_INFO << "Error inspecting PNG data: %s\n" << lodepng_error_text(status);
        return NULL;
    }

    if (s.Rle == -10)
        state.info_raw.colortype = LCT_PALETTE;
    else
        state.info_raw.colortype = LCT_RGBA;

    if (state.info_png.color.bitdepth == 16)
        state.info_raw.bitdepth = 16;
    else
        state.info_raw.bitdepth = 8;

    uint8_t *dstPx;
    status = lodepng_decode(&dstPx, &width, &height, &state, data, datasize);

    if (status != 0) {
        qCritical() << Q_FUNC_INFO << "Could not decode PNG image(%s)" << lodepng_error_text(status);
        return NULL;
    }
    s.W = width;
    s.H = height;
    return dstPx;
}

int readPcxHeader(Sprite &s, FILE *file, uint64_t offset)
{
    fseek(file, offset, SEEK_SET);
    uint16_t dummy;
    if (fread(&dummy, sizeof(uint16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading uint16_t dummy\n";
        return -1;
    }
    uint8_t encoding, bpp;
    if (fread(&encoding, sizeof(uint8_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading uint8_t encoding\n";
        return -1;
    }
    if (fread(&bpp, sizeof(uint8_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading uint8_t bpp\n";
        return -1;
    }
    if (bpp != 8) {
        qCritical() << Q_FUNC_INFO << "Invalid PCX color depth: expected 8-bit, got %d" << bpp;
        return -1;
    }
    uint16_t rect[4];
    if (fread(rect, sizeof(uint16_t), 4, file) != 4) {
        qCritical() << Q_FUNC_INFO << "Error reading rectangle\n";
        return -1;
    }
    fseek(file, offset + 66, SEEK_SET);
    uint16_t bpl;
    if (fread(&bpl, sizeof(uint16_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading bpl\n";
        return -1;
    }
    s.W = rect[2] - rect[0] + 1;
    s.H = rect[3] - rect[1] + 1;
    s.Rle = -1; // -1 for PCX
    return 0;
}

uint8_t *readSpriteDataV1(Sprite &s, FILE *file, Sff *sff, uint64_t offset, uint32_t datasize, uint32_t nextSubheader, Sprite *prev, bool c00)
{
    if (nextSubheader > offset) {
        // Ignore datasize except last
        datasize = nextSubheader - offset;
    }

    uint8_t ps;
    if (fread(&ps, sizeof(uint8_t), 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite ps data\n";
        return NULL;
    }
    bool paletteSame = ps != 0 && prev != NULL;
    if (readPcxHeader(s, file, offset) != 0) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite PCX header\n";
        return NULL;
    }

    fseek(file, offset + 128, SEEK_SET);
    // uint32_t palHash = 0;
    uint32_t palSize;
    if (c00 || paletteSame) {
        palSize = 0;
    } else {
        palSize = 768;
    }
    if (datasize < 128 + palSize) {
        datasize = 128 + palSize;
    }

    uint8_t *px = NULL;
    size_t srcLen = datasize - (128 + palSize);
    uint8_t *srcPx = NULL;
    if (srcLen > 0) {
        srcPx = (uint8_t *) malloc(srcLen);
    }
    if (!srcPx) {
        qCritical() << Q_FUNC_INFO << "Error allocating memory for sprite data\n";
        return NULL;
    }
    if (fread(srcPx, srcLen, 1, file) != 1) {
        qCritical() << Q_FUNC_INFO << "Error reading sprite PCX data pixel\n";
        free(srcPx);
        return NULL;
    }

    // printf("PCX: ps=%d ", ps);
    if (paletteSame) {
        if (prev != NULL) {
            s.PalIdx = prev->PalIdx;
            // printf("Info: Same palette (%d,%d) with (%d,%d) = %d\n", s->Group, s->Number, prev->Group, prev->Number, prev->palidx);
        }
        if (s.PalIdx < 0) {
            qCritical() << Q_FUNC_INFO << "Error: invalid prev palette index %d\n" << prev->PalIdx;
            free(srcPx);
            return NULL;
        }
        px = RlePcxDecode(s, srcPx, srcLen);
    } else {
        if (c00) {
            fseek(file, offset + datasize - 768, 0);
        }
        rgb_t pal_rgb[256];
        if (fread(pal_rgb, sizeof(pal_rgb), 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading palette rgb data\n";
            free(srcPx);
            return NULL;
        }
        // sff->palettes.emplace_back(generateTextureFromPaletteRGB(pal_rgb));
        sff->Palettes.emplace_back(generatePaletteFromRgbArray(pal_rgb));
        s.PalIdx = sff->Palettes.size() - 1;
        px = RlePcxDecode(s, srcPx, srcLen);
    }
    free(srcPx);
    return px;
}

bool isPalettedSprite(Sprite &s)
{
    return (s.Rle == -1 || s.Rle == -2 || s.Rle == -3 || s.Rle == -4 || s.Rle == -10);
}

bool isRGBASprite(Sprite &s)
{
    return (s.Rle == -11 || s.Rle == -12);
}

uint8_t *readSpriteDataV2(Sprite &s, FILE *file, uint64_t offset, uint32_t datasize, Sff *sff)
{
    uint8_t *px = NULL;
    if (s.Rle > 0) return NULL;

    if (s.Rle == 0) {
        px = (uint8_t *) malloc(datasize);
        if (!px) {
            qCritical() << Q_FUNC_INFO << "Error allocating memory for sprite data\n";
            return NULL;
        }
        // Read sprite data
        fseek(file, offset, SEEK_SET);
        if (fread(px, datasize, 1, file) != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading V2 uncompress sprite data\n";
            free(px);
            return NULL;
        }
    } else {
        size_t srcLen;
        uint8_t *srcPx = NULL;
        fseek(file, offset + 4, SEEK_SET);
        int format = -s.Rle;
        int rc;

        if (datasize < 4) {
            datasize = 4;
        }
        srcLen = datasize - 4;
        if (srcLen > 0) {
            srcPx = (uint8_t *) malloc(srcLen);
        }
        if (!srcPx) {
            qCritical() << Q_FUNC_INFO << "Error allocating memory for sprite data\n";
            return NULL;
        }
        rc = fread(srcPx, srcLen, 1, file);
        if (rc != 1) {
            qCritical() << Q_FUNC_INFO << "Error reading V2 RLE sprite data.\n";
            free(srcPx);
            return NULL;
        }

        switch (format) {
        case 2:
            px = Rle8Decode(s, srcPx, srcLen);
            break;
        case 3:
            px = Rle5Decode(s, srcPx, srcLen);
            break;
        case 4:
            px = Lz5Decode(s, srcPx, srcLen);
            break;
        case 10:
        case 11:
        case 12:
            px = PngDecode(s, srcPx, srcLen);
            break;
        }
        free(srcPx);
    }
    return px;
}

int LoadMugenSffFile(const char *filename, Sff *sff)
{
    FILE *file;
    file = fopen(filename, "rb");
    if (!file) {
        qCritical() << Q_FUNC_INFO << "Error: can not open file %s\n" << filename;
        return -1;
    }
    strncpy(sff->Filename, filename, 255);

    uint32_t lofs, tofs;
    if (readSffHeader(sff, file, &lofs, &tofs) != 0) {
        qCritical() << Q_FUNC_INFO << "Error: reading header %s\n" << filename;
        fclose(file);
        return -1;
    }

    // Print version
    if (sff->Header.Ver0 != 1) {
        std::map<std::array<int, 2>, int> uniquePals;
        sff->Palettes.clear();
        sff->Palettes.reserve(sff->Header.NumberOfPalettes);
        for (uint32_t i = 0; i < sff->Header.NumberOfPalettes; i++) {
            fseek(file, sff->Header.FirstPaletteHeaderOffset + i * 16, SEEK_SET);
            int16_t gn[3];
            if (fread(gn, sizeof(uint16_t), 3, file) != 3) {
                qCritical() << Q_FUNC_INFO << "Failed to read palette group: %s" << filename;
                fclose(file);
                return -1;
            }
            // printf("Palette %d: Group %d, Number %d, ColNumber %d\n", i, gn[0], gn[1], gn[2]);

            uint16_t link;
            if (fread(&link, sizeof(uint16_t), 1, file) != 1) {
                qCritical() << Q_FUNC_INFO << "Failed to read palette link: %s" << filename;
                fclose(file);
                return -1;
            }
            // printf("Palette link: %d\n", link);

            uint32_t ofs, siz;
            if (fread(&ofs, sizeof(uint32_t), 1, file) != 1) {
                qCritical() << Q_FUNC_INFO << "Failed to read palette offset: %s" << filename;
                fclose(file);
                return -1;
            }
            if (fread(&siz, sizeof(uint32_t), 1, file) != 1) {
                qCritical() << Q_FUNC_INFO << "Failed to read palette size: %s" << filename;
                fclose(file);
                return -1;
            }

            // Check if the palette is unique
            std::array<int, 2> key = { gn[0], gn[1] };
            if (uniquePals.find(key) == uniquePals.end()) {
                fseek(file, lofs + ofs, SEEK_SET);
                uint32_t rgba[256];
                if (fread(rgba, sizeof(uint32_t), 256, file) != 256) {
                    qCritical() << Q_FUNC_INFO << "Failed to read palette data: %s" << filename;
                    fclose(file);
                    return -1;
                }
                const Palette &pa = generatePaletteFromRgbaArray(rgba);
                sff->Palettes.push_back(pa);
                uniquePals[key] = i;
            } else {
                // If the palette is not unique, use the existing one
                qDebug() << Q_FUNC_INFO << "Palette %d(%d,%d) is not unique, using palette %d\nUntested code"
                         << i << gn[0] << gn[1] << uniquePals[key];
                const Palette &pa = sff->Palettes[uniquePals[key]];
                sff->Palettes.push_back(pa);
            }
        }
    }

    sff->Sprites.clear();
    sff->Sprites.reserve(sff->Header.NumberOfSprites);
    Sprite *prev = NULL;
    sff->NumLinkedSprites = 0;
    long shofs = sff->Header.FirstSpriteHeaderOffset;
    for (uint32_t i = 0; i < sff->Header.NumberOfSprites; i++) {
        Sprite sprite;
        sff->Sprites.push_back(sprite);
        uint32_t xofs = 0, size = 0;
        uint16_t indexOfPrevious = 0;
        fseek(file, shofs, SEEK_SET);
        switch (sff->Header.Ver0) {
        case 1:
            if (readSpriteHeaderV1(sff->Sprites[i], file, &xofs, &size, &indexOfPrevious) != 0) {
                fclose(file);
                return -1;
            }
            break;
        case 2:
            if (readSpriteHeaderV2(sff->Sprites[i], file, &xofs, &size, lofs, tofs, &indexOfPrevious) != 0) {
                fclose(file);
                return -1;
            }
            break;
        }

        if (size == 0) {
            sff->NumLinkedSprites++;
            if (indexOfPrevious < i) {
                spriteCopy(sff->Sprites[i], sff->Sprites[indexOfPrevious]);
                // printf("Info: Sprite[%d] use prev Sprite[%d]\n", i, indexOfPrevious);
            } else {
                qDebug() << Q_FUNC_INFO << "Warning: Sprite %d has no size\n" << i;
                sff->Sprites[i].PalIdx = 0;
            }
        } else {
            uint8_t *data = NULL;
            int palette_used = -1;
            int compression_format_used = -1;
            bool character = true;
            switch (sff->Header.Ver0) {
            case 1:
                data = readSpriteDataV1(sff->Sprites[i], file, sff, shofs + 32, size, xofs, prev, character);
                if (!data) {
                    qCritical() << Q_FUNC_INFO << "Error reading sprite v1 data\n";
                    fclose(file);
                    return -1;
                }
                palette_used = sff->Sprites[i].PalIdx;
                sff->PaletteUsage[palette_used]++;
                break;
            case 2:
                data = readSpriteDataV2(sff->Sprites[i], file, xofs, size, sff);
                if (!data) {
                    qCritical() << Q_FUNC_INFO << "Error reading sprite v2 data\n";
                    fclose(file);
                    return -1;
                }
                if (isPalettedSprite(sff->Sprites[i])) {
                    palette_used = sff->Sprites[i].PalIdx;
                    sff->PaletteUsage[palette_used]++;
                }
                break;
            default:
                qCritical() << Q_FUNC_INFO << "Warning: unsupported sprite version: %d.x.x.x\n" << sff->Header.Ver0;
                break;
            }
            compression_format_used = sff->Sprites[i].Rle;
            sff->CompressionFormatUsage[compression_format_used]++;
            if (data) {
                if (isRGBASprite(sff->Sprites[i]))  // PNG Image (RGBA)
                    // sff->sprites[i].texture_id = generateTextureRGBAFromSprite(sff->sprites[i].Size[0], sff->sprites[i].Size[1], data);
                    sff->Sprites[i].ColorList = generateColorListFromRgbaData(sff->Sprites[i].W, sff->Sprites[i].H, data);
                else     { // Paletted Image (R only)
                    // sff->sprites[i].texture_id = generateTextureFromSprite(sff->sprites[i].Size[0], sff->sprites[i].Size[1], data);
                    Sprite &sprite = sff->Sprites[i];
                    sprite.ColorList = generateColorListFromPaletteData(sprite.W, sprite.H,
                                                                        data, sff->Palettes.at(sprite.PalIdx));
                }
                free(data);
            }

            // if use previous sprite Group 9000 and Number 0 only (fix for SFF v1)
            if (sff->Sprites[i].Group == 9000) {
                if (sff->Sprites[i].Number == 0) {
                    prev = &sff->Sprites[i];
                }
            } else {
                prev = &sff->Sprites[i];
            }
        }

        if (sff->Header.Ver0 == 1) {
            shofs = xofs;
        } else {
            shofs += 28;
        }

        // printSprite(&sff->sprites[i]);
    }

    // if SFF == v1 then update total palette
    if (sff->Header.Ver0 == 1) {
        sff->Header.NumberOfPalettes = sff->Palettes.size();
    }

    return 0;
}
